package com.ruoyi.system.privilege;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import com.ruoyi.common.utils.SecurityUtils;
import com.ruoyi.common.utils.StringUtils;
import com.ruoyi.system.domain.DataPermissionRule;
import com.ruoyi.system.privilege.bo.Group;
import com.ruoyi.system.privilege.bo.Rule;
import net.sf.jsqlparser.JSQLParserException;
import net.sf.jsqlparser.expression.*;
import net.sf.jsqlparser.expression.operators.conditional.AndExpression;
import net.sf.jsqlparser.expression.operators.conditional.OrExpression;
import net.sf.jsqlparser.expression.operators.relational.*;
import net.sf.jsqlparser.parser.CCJSqlParserUtil;
import net.sf.jsqlparser.schema.Column;
import net.sf.jsqlparser.schema.Table;
import net.sf.jsqlparser.statement.select.Join;
import net.sf.jsqlparser.statement.select.PlainSelect;
import net.sf.jsqlparser.statement.select.Select;
import org.apache.commons.text.StringSubstitutor;
import org.springframework.stereotype.Component;

import java.text.SimpleDateFormat;
import java.util.*;
import java.util.stream.Collectors;

import static com.ruoyi.system.privilege.OperatorConstant.*;

/**
 * 规则解析器
 *
 * @author dbq
 * @date 2021/9/10 09:38
 */
@Component
public class RuleParser {

    public String toJson(List<Group> groups) {
        return JSONObject.toJSONString(groups);
    }

    /**
     * 增强sql查询条件
     *
     * @param permissions 权限规则
     * @param sql         原始sql
     * @return 增强后的select
     * @throws JSQLParserException sql异常
     */
    public static String enhanceWhere(JSONArray permissions, String sql, String point) throws JSQLParserException {
        Set<String> pointSet = new HashSet<>(Arrays.asList(point.split(",")));
        Select select = (Select) CCJSqlParserUtil.parse(sql);
        PlainSelect plainSelect = (PlainSelect) select.getSelectBody();

        /*
          where 原条件  and 检测点1(
                     分组1 and 分组2
          )
                       and 检测点2(
                     分组1 and 分组2
          ）

          检测点与检测点之间、组与组之间都添加了括号，确保sql执行的优先级
         */
        for (Object permission : permissions) {
            //权限检测点表达式
            Expression pointExpression = null;
            DataPermissionRule rule = JSON.parseObject(String.valueOf(permission), DataPermissionRule.class);
            //为空默认适配所有规则
            if (pointSet.isEmpty() || pointSet.contains(rule.getPoint())) {
                JSONArray groups = JSONArray.parseArray(rule.getRule());
                //处理一个权限检测点内部的多个分组
                for (Object o : groups) {
                    Group group = JSON.parseObject(String.valueOf(o), Group.class);
                    Expression ruleExpression = parserRules(group.getRules(), select);
                    if (AND.equalsIgnoreCase(group.getOp())) {
                        pointExpression = pointExpression == null ? ruleExpression : new AndExpression(pointExpression, ruleExpression);
                    }
                    if (OR.equalsIgnoreCase(group.getOp())) {
                        pointExpression = pointExpression == null ? ruleExpression : new OrExpression(pointExpression, ruleExpression);
                    }
                }
                Parenthesis pointParenthesis = new Parenthesis(pointExpression);
                Expression where = plainSelect.getWhere();
                where = where == null ? pointParenthesis : new AndExpression(where, pointParenthesis);
                plainSelect.setWhere(where);
            }
        }

        String enhanceSql = select.getSelectBody().toString();
        enhanceSql = fillParam(enhanceSql);
        return enhanceSql;
    }

    /**
     * 解析单个分组内的一组规则: 分组内部通过 and 连接，分组与分组之间通过自定义连接符连接
     */
    private static Parenthesis parserRules(List<Rule> rules, Select select) throws JSQLParserException {
        Expression expression = null;
        for (int i = 0; i < rules.size(); i++) {
            Rule r = rules.get(i);
            ComparisonOperator subExpress = null;
            String field = SqlParserUtil.createFieldWithTableAlias(select, r.getField());
            if ("role_id".equalsIgnoreCase(field)) {
                processRole(select, r.getValue());
                continue;
            }
            if ("dept_id".equalsIgnoreCase(field)) {
                processDept(select, r.getValue());
                continue;
            }
            switch (r.getOp()) {
                case EQ:
                    subExpress = new EqualsTo();
                    break;
                case GE:
                    subExpress = new GreaterThanEquals();
                    break;
                case LE:
                    subExpress = new MinorThanEquals();
                    break;
                case GT:
                    subExpress = new GreaterThan();
                    break;
                case LT:
                    subExpress = new MinorThan();
                    break;
                default:
                    break;
            }
            subExpress.setLeftExpression(new Column(field));
            subExpress.setRightExpression(new StringValue(r.getValue()));
            if (expression == null) {
                expression = subExpress;
            } else {
                expression = new AndExpression(expression, subExpress);
            }
        }

        Parenthesis parenthesis = new Parenthesis(expression);
        return parenthesis;
    }

    /**
     * 对角色进行特殊处理
     * 如果权限规则中包含角色信息，需要外联用户角色表来进行数据行过滤
     */
    private static void processRole(Select select, String roleId) {
        PlainSelect plainSelect = (PlainSelect) select.getSelectBody();
        Expression where = plainSelect.getWhere();

        //增加join信息
        final String tableName = "sys_user_role sur";
        Table leftJoinTable = new Table(tableName);
        Join userRoleJoin = new Join();
        userRoleJoin.withRightItem(leftJoinTable);
        EqualsTo joinEqualsTo = new EqualsTo();
        joinEqualsTo.setLeftExpression(new Column("sur.user_id "));
        joinEqualsTo.setRightExpression(new Column(SqlParserUtil.createFieldWithTableAlias(select, "user_id")));
        userRoleJoin.withOnExpression(joinEqualsTo);
        plainSelect.addJoins(userRoleJoin);

        //增加where信息
        EqualsTo equalsTo = new EqualsTo();
        equalsTo.setLeftExpression(new Column("sur.role_id "));
        equalsTo.setRightExpression(new LongValue(roleId));
        where = where == null ? equalsTo : new AndExpression(where, equalsTo);
        plainSelect.setWhere(where);
    }

    /**
     * 对部门进行特殊处理
     * 如果权限规则中包含部门信息，需要外联用户表来进行数据行过滤
     */
    private static void processDept(Select select, String deptIds) {
        PlainSelect plainSelect = (PlainSelect) select.getSelectBody();
        Expression where = plainSelect.getWhere();

        final String tableName = "sys_user";
        String alias = "sys_user";
        //是否包含人员表外联join
        boolean contains = false;

        List<Join> joins = SqlParserUtil.getSelectJoins(select);
        for (Join join : joins) {
            String[] tableAndAlias = join.getRightItem().toString().replaceAll("\\s+", " ").split(" ");
            if (!StringUtils.isEmpty(tableAndAlias[0]) && tableAndAlias[0].equalsIgnoreCase(tableName)) {
                contains = true;
                if (tableAndAlias.length >= 2 && !StringUtils.isEmpty(tableAndAlias[1])) {
                    alias = tableAndAlias[1];
                }
                break;
            }
        }

        //如果原有sql中不包含人员表的关联信息，则构造一个关联信息
        if (!contains) {
            Table leftJoinTable = new Table(tableName);
            leftJoinTable.setAlias(new Alias(alias));
            Join userRoleJoin = new Join();
            userRoleJoin.withRightItem(leftJoinTable);
            EqualsTo joinEqualsTo = new EqualsTo();
            joinEqualsTo.setLeftExpression(new Column(alias + ".user_id "));
            joinEqualsTo.setRightExpression(new Column(SqlParserUtil.createFieldWithTableAlias(select, "user_id")));
            userRoleJoin.withOnExpression(joinEqualsTo);
            plainSelect.addJoins(userRoleJoin);
        }

        //增加where信息
        ItemsList itemsList = new ExpressionList(Arrays.stream(deptIds.split(",")).distinct().map(LongValue::new).collect(Collectors.toList()));
        Expression inExpression = new InExpression(new Column(alias + ".dept_id "), itemsList);
        where = where == null ? inExpression : new AndExpression(where, inExpression);
        plainSelect.setWhere(where);
    }

    /**
     * 填充变量参数
     *
     * @param sql 增强后sql
     * @return 填充参数后的sql
     */
    private static String fillParam(String sql) {
        Map<String, Object> params = new HashMap<>();
        SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd hh:mm:ss");
        params.put("userId", SecurityUtils.getLoginUser().getUser().getUserId());
        params.put("current", sdf.format(new Date()));
        sql = replace(sql, params, "${", "}", true);
        return sql;
    }

    private static String replace(String source, Map<String, Object> parameter, String prefix, String suffix, boolean enableSubstitutionInVariables) {
        StringSubstitutor strSubstitutor = new StringSubstitutor(parameter, prefix, suffix);
        strSubstitutor.setEnableSubstitutionInVariables(enableSubstitutionInVariables);
        return strSubstitutor.replace(source);
    }
}
